package cn.backflow.generator.provider.db.table.model;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import cn.backflow.generator.GeneratorProperties;
import cn.backflow.generator.provider.db.table.TableFactory;
import cn.backflow.generator.util.StringHelper;

/**
 * 用于生成代码的Table对象.对应数据库的table
 * @author badqiu
 * @email badqiu(a)gmail.com
 */
public class Table implements java.io.Serializable, Cloneable {

	private String sqlName;
	private String remarks;
	private String className;

	/** the name of the owner of the synonym if this table is a synonym */
	private String ownerSynonymName = null;
	/** real table name for oracle SYNONYM */
	private String tableSynonymName = null;

	private LinkedHashSet<Column> columns = new LinkedHashSet<>();
	private List<Column> primaryKeyColumns = new ArrayList<>();

	public Table() {}

	public Table(Table t) {
		setSqlName(t.getSqlName());
		this.remarks = t.getRemarks();
		this.className = t.getClassName();
		this.ownerSynonymName = t.getOwnerSynonymName();
		this.columns = t.getColumns();
		this.primaryKeyColumns = t.getPkColumns();
		this.tableAlias = t.getTableAlias();
		this.exportedKeys = t.exportedKeys;
		this.importedKeys = t.importedKeys;
	}

	public LinkedHashSet<Column> getColumns() {
		return columns;
	}

	public void setColumns(LinkedHashSet<Column> columns) {
		this.columns = columns;
	}

	public String getOwnerSynonymName() {
		return ownerSynonymName;
	}

	public void setOwnerSynonymName(String ownerSynonymName) {
		this.ownerSynonymName = ownerSynonymName;
	}

	public String getTableSynonymName() {
		return tableSynonymName;
	}

	public void setTableSynonymName(String tableSynonymName) {
		this.tableSynonymName = tableSynonymName;
	}

	public void setPkColumns(List<Column> primaryKeyColumns) {
		this.primaryKeyColumns = primaryKeyColumns;
	}

	/** 数据库中表的表名称,其它属性很多都是根据此属性派生 */
	public String getSqlName() {
		return sqlName;
	}

	public void setSqlName(String sqlName) {
		this.sqlName = sqlName;
	}

	public static String removeTableSqlNamePrefix(String sqlName) {
		String prefixs = GeneratorProperties.getProperty("tableRemovePrefixes", "");
		for (String prefix : prefixs.split(",")) {
			String removedPrefixSqlName = StringHelper.removePrefix(sqlName, prefix, true);
			if (!removedPrefixSqlName.equals(sqlName)) {
				return removedPrefixSqlName;
			}
		}
		return sqlName;
	}

	/** 数据库中表的表备注 */
	private String getRemarks() {
		return remarks;
	}

	public void setRemarks(String remarks) {
		this.remarks = remarks;
	}

	public void addColumn(Column column) {
		columns.add(column);
	}

	public void setClassName(String customClassName) {
		this.className = customClassName;
	}

	/**
	 * 根据sqlName得到的类名称，示例值: UserInfo
	 */
	public String getClassName() {
		if (StringHelper.isBlank(className)) {
			String removedPrefixSqlName = removeTableSqlNamePrefix(sqlName);
			return StringHelper.makeAllWordFirstLetterUpperCase(StringHelper
					.toUnderscoreName(removedPrefixSqlName));
		} else {
			return className;
		}
	}

	/** 数据库中表的别名，等价于: getRemarks().isEmpty() ? getClassName() : getRemarks() */
	private String getTableAlias() {
		if (StringHelper.isNotBlank(tableAlias))
			return tableAlias;
		return StringHelper.removeCrlf(StringHelper.defaultIfEmpty(getRemarks(), getClassName()));
	}

	public void setTableAlias(String v) {
		this.tableAlias = v;
	}

	/**
	 * 等价于getClassName().toLowerCase()
	 */
	public String getClassNameLowerCase() {
		return getClassName().toLowerCase();
	}

	/**
	 * 得到用下划线分隔的类名称，如className=UserInfo,则underscoreName=user_info
	 */
	public String getUnderscoreName() {
		return StringHelper.toUnderscoreName(getClassName()).toLowerCase();
	}

	/**
	 * 返回值为getClassName()的第一个字母小写,如className=UserInfo,则ClassNameFirstLower=
	 * userInfo
	 */
	public String getClassNameFirstLower() {
		return StringHelper.uncapitalize(getClassName());
	}

	/**
	 * 根据getClassName()计算而来,用于得到常量名,如className=UserInfo,则constantName=USER_INFO
	 */
	public String getConstantName() {
		return StringHelper.toUnderscoreName(getClassName()).toUpperCase();
	}

	/**
	 * 得到主键总数
	 */
	int getPkCount() {
		int pkCount = 0;
		for (Column c : columns) {
			if (c.isPk()) {
				pkCount++;
			}
		}
		return pkCount;
	}

	/**
	 * 得到是主键的全部column
	 */
	List<Column> getPkColumns() {
		List<Column> results = new ArrayList<>();
		for (Column c : getColumns())
			if (c.isPk())
				results.add(c);
		return results;
	}

	/**
	 * 得到不是主键的全部column
	 */
	public List<Column> getNotPkColumns() {
		List<Column> results = new ArrayList<>();
		for (Column c : getColumns()) {
			if (!c.isPk())
				results.add(c);
		}
		return results;
	}

	/** 得到单主键，等价于getPkColumns().get(0) */
	private Column getPkColumn() {
		if (getPkColumns().isEmpty()) {
			throw new IllegalStateException("not found primary key on table:" + getSqlName());
		}
		return getPkColumns().get(0);
	}

	/** 使用 getPkColumn()替换 */
	@Deprecated
	public Column getIdColumn() {
		return getPkColumn();
	}

	public List<Column> getEnumColumns() {
		List<Column> results = new ArrayList<>();
		for (Column c : getColumns()) {
			if (!c.isEnumColumn())
				results.add(c);
		}
		return results;
	}

	public Column getColumnByName(String name) {
		Column c = getColumnBySqlName(name);
		if (c == null) {
			c = getColumnBySqlName(StringHelper.toUnderscoreName(name));
		}
		return c;
	}

	public Column getColumnBySqlName(String sqlName) {
		for (Column c : getColumns()) {
			if (c.getSqlName().equalsIgnoreCase(sqlName)) {
				return c;
			}
		}
		return null;
	}

	public Column getRequiredColumnBySqlName(String sqlName) {
		if (getColumnBySqlName(sqlName) == null) {
			throw new IllegalArgumentException("not found column with sqlName:" + sqlName + " on table:"
					+ getSqlName());
		}
		return getColumnBySqlName(sqlName);
	}

	/**
	 * 忽略过滤掉某些关键字的列,关键字不区分大小写,以逗号分隔
	 * @param ignoreKeywords
	 */
	public List<Column> getIgnoreKeywordsColumns(String ignoreKeywords) {
		List<Column> results = new ArrayList<>();
		for (Column c : getColumns()) {
			String sqlname = c.getSqlName().toLowerCase();
			if (StringHelper.contains(sqlname, ignoreKeywords.split(","))) {
				continue;
			}
			results.add(c);
		}
		return results;
	}

	/**
	 * This method was created in VisualAge.
	 */
	public void initImportedKeys(DatabaseMetaData dbmd) throws java.sql.SQLException {

		// get imported keys a

		ResultSet fkeys = dbmd.getImportedKeys(catalog, schema, this.sqlName);

		while (fkeys.next()) {
			String pktable = fkeys.getString(PKTABLE_NAME);
			String pkcol = fkeys.getString(PKCOLUMN_NAME);
			String fktable = fkeys.getString(FKTABLE_NAME);
			String fkcol = fkeys.getString(FKCOLUMN_NAME);
			String seq = fkeys.getString(KEY_SEQ);
			Integer iseq = new Integer(seq);
			getImportedKeys().addForeignKey(pktable, pkcol, fkcol, iseq);
		}
		fkeys.close();
	}

	/**
	 * This method was created in VisualAge.
	 */
	public void initExportedKeys(DatabaseMetaData dbmd) throws java.sql.SQLException {
		// get Exported keys

		ResultSet fkeys = dbmd.getExportedKeys(catalog, schema, this.sqlName);

		while (fkeys.next()) {
			String pktable = fkeys.getString(PKTABLE_NAME);
			String pkcol = fkeys.getString(PKCOLUMN_NAME);
			String fktable = fkeys.getString(FKTABLE_NAME);
			String fkcol = fkeys.getString(FKCOLUMN_NAME);
			String seq = fkeys.getString(KEY_SEQ);
			Integer iseq = new Integer(seq);
			getExportedKeys().addForeignKey(fktable, fkcol, pkcol, iseq);
		}
		fkeys.close();
	}

	/**
	 * @return Returns the exportedKeys.
	 */
	ForeignKeys getExportedKeys() {
		if (exportedKeys == null) {
			exportedKeys = new ForeignKeys(this);
		}
		return exportedKeys;
	}

	/**
	 * @return Returns the importedKeys.
	 */
	ForeignKeys getImportedKeys() {
		if (importedKeys == null) {
			importedKeys = new ForeignKeys(this);
		}
		return importedKeys;
	}

	@Override
	public String toString() {
		return "Database Table:" + getSqlName() + " to ClassName:" + getClassName();
	}

	@Override
	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			//ignore
			return null;
		}
	}

	private String catalog = TableFactory.getInstance().getCatalog();
	private String schema = TableFactory.getInstance().getSchema();

	private String tableAlias;
	private ForeignKeys exportedKeys;
	private ForeignKeys importedKeys;

	private static final String PKTABLE_NAME = "PKTABLE_NAME";
	private static final String PKCOLUMN_NAME = "PKCOLUMN_NAME";
	private static final String FKTABLE_NAME = "FKTABLE_NAME";
	private static final String FKCOLUMN_NAME = "FKCOLUMN_NAME";
	private static final String KEY_SEQ = "KEY_SEQ";
}